package com.alibaba.doris.client.net.netty;
import static org.jboss.netty.channel.Channels.write;
import java.io.IOException;
import java.nio.channels.NotYetConnectedException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelDownstreamHandler;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.doris.client.net.command.BaseCommand;
import com.alibaba.doris.client.net.command.CheckQueueCommand;
import com.alibaba.doris.client.net.command.Command;
import com.alibaba.doris.client.net.command.ErrorType;
import com.alibaba.doris.client.net.protocol.ProtocolParser;
/**
* @author ajun Email:jack.yuj@alibaba-inc.com
*/
public class DorisClientEncodeAndDecodeHandler extends FrameDecoder implements ChannelDownstreamHandler {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
Throwable t = e.getCause();
if (t instanceof IOException) {
processChannelClosed(ctx);
} else if (t instanceof NotYetConnectedException) {
processChannelClosed(ctx);
}
super.exceptionCaught(ctx, e);
}
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
// Encode
if (!(e instanceof MessageEvent)) {
ctx.sendDownstream(e);
return;
}
MessageEvent evt = (MessageEvent) e;
Command<?> command = (Command<?>) evt.getMessage();
if (needCheckCommandQueue) {
// 插入一条check命令;
writeCommand(ctx, evt, new CheckQueueCommand());
needCheckCommandQueue = false;
logger.error("Send CheckQueueCommand. to channel:" + ctx.getChannel());
}
writeCommand(ctx, evt, command);
}
private void writeCommand(ChannelHandlerContext ctx, MessageEvent evt, Command<?> command) throws Exception {
Object encodedMessage = encode(ctx, evt.getChannel(), command);
if (command == encodedMessage) {
ctx.sendDownstream(evt);
} else if (encodedMessage != null) {
try {
boolean closeChannel = false;
ChannelFuture feture = evt.getFuture();
write(ctx, feture, encodedMessage, evt.getRemoteAddress());
// 等待60s超时
if (feture.await(60000)) {
if (feture.isSuccess()) {
writeFailureCount = 0;
commandQueue.offer(command);
if (logger.isDebugEnabled()) {
logger.debug("Send command:" + command + " to " + ctx.getChannel().getRemoteAddress());
}
return;
}
writeFailureCount++;
if (writeFailureCount > MAX_WRITE_FAILURE_TIMES) {
// 如果连续多次写入数据失败,强制关闭通道;
closeChannel = true;
}
} else {
// 如果等待60s还无法写入数据则强制关闭通道;
closeChannel = true;
}
if (command instanceof BaseCommand<?>) {
BaseCommand<?> baseCommand = (BaseCommand<?>) command;
baseCommand.setErrorMessage("Send command failed!");
}
if (logger.isDebugEnabled()) {
logger.debug("Send command failed:" + command);
}
command.complete();
if (closeChannel) {
Channel channel = ctx.getChannel();
if (channel.isOpen()) {
logger.error("Couldn't sent command to:" + channel + "; close channel;");
// 如果连续往通道写数据失败;则尝试关闭通道;
Channels.close(channel).awaitUninterruptibly(1000);
}
}
} catch (Exception exception) {
if (command instanceof BaseCommand<?>) {
BaseCommand<?> baseCommand = (BaseCommand<?>) command;
baseCommand.setErrorMessage("Send command failed!");
}
// 往通道写入数据失败需要通知命令写入数据失败,并从等待结果队列移除命令;
writeFailureCount++;
command.complete();
logger.error("Send command failed:" + command);
throw exception;
}
}
}
private long writeFailureCount = 0;
private int MAX_WRITE_FAILURE_TIMES = 10;
protected Object encode(ChannelHandlerContext ctx, Channel channel, Command<?> command) throws Exception {
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory());
ProtocolParser parser = command.getProtocolParser();
try {
parser.encode(command, buffer);
// if (logger.isDebugEnabled()) {
// logger.debug("Encoding command:" + command);
// }
} catch (Exception e) {
if (command instanceof BaseCommand<?>) {
BaseCommand<?> baseCommand = (BaseCommand<?>) command;
baseCommand.setErrorMessage("Encoding command failed!");
}
command.complete();
throw e;
}
return buffer;
}
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
if (!buffer.readable()) {
return null;
}
if (null == currentCommand) {
// 防止阻塞netty的worker线程;
currentCommand = commandQueue.poll(100, TimeUnit.MILLISECONDS);
if (null == currentCommand) {
logger.error("Failed to receive command from queue. (The queue size is 0.) channel:" + channel
+ " buffer.readableBytes:" + buffer.readableBytes());
checkCommandQueueIsOk();
return null;
}
}
ProtocolParser parser = currentCommand.getProtocolParser();
try {
if (parser.decode(currentCommand, buffer)) {
Command<?> rCommand = currentCommand;
currentCommand = null;
if (logger.isDebugEnabled()) {
logger.debug("Received command:" + rCommand + " from " + channel + " queue size:"
+ commandQueue.size());
}
appendExtraErrorInformationForFailure(channel, rCommand);
if (rCommand instanceof CheckQueueCommand) {
// 检查通道命令直接忽略;
logger.error("Received checkQueueCommand: from " + channel + "queue size:" + commandQueue.size()
+ ", skip bytes:" + ((CheckQueueCommand) rCommand).getDiscardBytes());
return null;
}
return rCommand;
}
} catch (Throwable e) {
logger.error("Received command failed:" + currentCommand + " from " + channel, e);
// 解析当前命令数据出错,直接返回出错的命令。TODO流中没处理完的数据如何处理?
if (currentCommand != null) {
Command<?> rCommand = currentCommand;
currentCommand = null;
appendExtraErrorInformationForFailure(channel, rCommand);
return rCommand;
}
}
return null;
}
private void appendExtraErrorInformationForFailure(Channel channel, Command<?> rCommand) {
if (rCommand instanceof BaseCommand) {
BaseCommand<?> baseCommand = (BaseCommand<?>) rCommand;
if (!baseCommand.isSuccess() && ErrorType.VERSION_OUT_OF_DATE != baseCommand.getErrorType()) {
baseCommand.setErrorMessage(baseCommand.getErrorMessage() + " IP:" + channel.getRemoteAddress());
}
}
}
private void checkCommandQueueIsOk() {
long currentTime = System.currentTimeMillis();
if (lastEmptyQueueTime == 0) {
lastEmptyQueueTime = currentTime;
}
this.timesOfFetchEmptyQueue++;
// 如果怀疑队列中的数据存在问题,通知插入检查通道是否正常的命令;
if ((currentTime - lastEmptyQueueTime) <= MAX_STATISTIC_INTERVAL) {
if (timesOfFetchEmptyQueue >= MAX_FETCH_EMPTY_COMMAND_TIMES) {
needCheckCommandQueue = true;
lastEmptyQueueTime = 0;
timesOfFetchEmptyQueue = 0;
logger.error("To notify send CheckQueueCommand.");
}
}
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
processChannelClosed(ctx);
super.channelClosed(ctx, e);
}
private void processChannelClosed(ChannelHandlerContext ctx) {
// channel关闭时,需要清空队列中剩余的数据。 current command也要处理??
Command<?> command = null;
if (null != currentCommand) {
notifyCommandThatChannelHasClosed(ctx, currentCommand);
currentCommand = null;
}
while ((command = commandQueue.poll()) != null) {
notifyCommandThatChannelHasClosed(ctx, command);
}
}
private void notifyCommandThatChannelHasClosed(ChannelHandlerContext ctx, Command<?> command) {
if (command instanceof BaseCommand<?>) {
BaseCommand<?> baseCommand = (BaseCommand<?>) command;
baseCommand.setSuccess(false);
baseCommand.setErrorMessage("Connection closed! remoteAddress:" + ctx.getChannel().getRemoteAddress());
baseCommand.setErrorType(ErrorType.CONNECTION);
}
command.complete();
}
private final static long MAX_STATISTIC_INTERVAL = 60 * 1000; // 60
private final static int MAX_FETCH_EMPTY_COMMAND_TIMES = 1; // 设置成1退化成每次没有接收到数据都插入一条check命令;
// seconds;
private long lastEmptyQueueTime;
private int timesOfFetchEmptyQueue;
private volatile boolean needCheckCommandQueue = false;
private Command<?> currentCommand;
private BlockingQueue<Command<?>> commandQueue = new ArrayBlockingQueue<Command<?>>(5000);
private static final Logger logger = LoggerFactory.getLogger(DorisClientEncodeAndDecodeHandler.class);
}